Area Data I

NOTE: You can download the source files for this book from here. The source files are in the format of R Notebooks. Notebooks are pretty neat, because the allow you execute code within the notebook, so that you can work interactively with the notes.

In last few practices/sessions, you learned about spatial point patterns. The next few sessions will concentrate on area data.

If you wish to work interactively with this chapter you will need the following:

Learning objectives

In this practice, you will learn:

  1. A formal definition of area data.
  2. Processes and area data.
  3. Visualizing area data: Choropleth maps.
  4. Visualizing area data: Cartograms.

Suggested reading

O’Sullivan D and Unwin D (2010) Geographic Information Analysis, 2nd Edition, Chapter 7. John Wiley & Sons: New Jersey.

Preliminaries

As usual, it is good practice to clear the working space to make sure that you do not have extraneous items there when you begin your work. The command in R to clear the workspace is rm (for “remove”), followed by a list of items to be removed. To clear the workspace from all objects, do the following:

rm(list = ls())

Note that ls() lists all objects currently on the worspace.

Load the libraries you will use in this activity:

library(tidyverse)
#library(rgdal)
#library(broom)
library(plotly)
library(cartogram)
library(gridExtra)
library(geog4ga3)

Read the data used in this chapter.

data("Hamilton_CT")

The dataframe includes the spatial information for the census tracts in the Hamilton Census Metropolitan Area in Canada (as polygons), and a series of demographic variables from the 2011 Census of Canada.

You can quickly verify the contents of the dataframe by means of summary:

summary(Hamilton_CT)
       ID               AREA             TRACT             POPULATION     POP_DENSITY       
 Min.   : 919807   Min.   :  0.3154   Length:188         Min.   :    5   Min.   :    2.591  
 1st Qu.: 927964   1st Qu.:  0.8552   Class :character   1st Qu.: 2639   1st Qu.: 1438.007  
 Median : 948130   Median :  1.4157   Mode  :character   Median : 3595   Median : 2689.737  
 Mean   : 948710   Mean   :  7.4578                      Mean   : 3835   Mean   : 2853.078  
 3rd Qu.: 959722   3rd Qu.:  2.7775                      3rd Qu.: 4692   3rd Qu.: 3783.889  
 Max.   :1115750   Max.   :138.4466                      Max.   :11675   Max.   :14234.286  
  AGE_LESS_20      AGE_20_TO_24    AGE_25_TO_29    AGE_30_TO_34     AGE_35_TO_39   
 Min.   :   0.0   Min.   :  0.0   Min.   :  0.0   Min.   :   0.0   Min.   :   0.0  
 1st Qu.: 528.8   1st Qu.:168.8   1st Qu.:135.0   1st Qu.: 135.0   1st Qu.: 145.0  
 Median : 750.0   Median :225.0   Median :215.0   Median : 195.0   Median : 200.0  
 Mean   : 899.3   Mean   :253.9   Mean   :232.8   Mean   : 228.2   Mean   : 239.6  
 3rd Qu.:1110.0   3rd Qu.:311.2   3rd Qu.:296.2   3rd Qu.: 281.2   3rd Qu.: 280.0  
 Max.   :3285.0   Max.   :835.0   Max.   :915.0   Max.   :1320.0   Max.   :1200.0  
  AGE_40_TO_44     AGE_45_TO_49    AGE_50_TO_54    AGE_55_TO_59    AGE_60_TO_64  AGE_65_TO_69  
 Min.   :   0.0   Min.   :  0.0   Min.   :  0.0   Min.   :  0.0   Min.   :  0   Min.   :  0.0  
 1st Qu.: 170.0   1st Qu.:203.8   1st Qu.:203.8   1st Qu.:175.0   1st Qu.:140   1st Qu.:115.0  
 Median : 230.0   Median :282.5   Median :280.0   Median :240.0   Median :220   Median :157.5  
 Mean   : 268.7   Mean   :310.6   Mean   :300.3   Mean   :257.7   Mean   :229   Mean   :174.2  
 3rd Qu.: 325.0   3rd Qu.:385.0   3rd Qu.:375.0   3rd Qu.:325.0   3rd Qu.:295   3rd Qu.:221.2  
 Max.   :1105.0   Max.   :880.0   Max.   :740.0   Max.   :625.0   Max.   :540   Max.   :625.0  
  AGE_70_TO_74    AGE_75_TO_79     AGE_80_TO_84     AGE_MORE_85              geometry  
 Min.   :  0.0   Min.   :  0.00   Min.   :  0.00   Min.   :  0.00   POLYGON      :188  
 1st Qu.: 90.0   1st Qu.: 68.75   1st Qu.: 50.00   1st Qu.: 35.00   epsg:NA      :  0  
 Median :130.0   Median :100.00   Median : 77.50   Median : 70.00   +proj=long...:  0  
 Mean   :139.7   Mean   :118.32   Mean   : 95.05   Mean   : 87.71                      
 3rd Qu.:180.0   3rd Qu.:160.00   3rd Qu.:120.00   3rd Qu.:105.00                      
 Max.   :540.0   Max.   :575.00   Max.   :420.00   Max.   :400.00                      

Area data

Every phenomena can be measured at a location (ask yourself, what exists outside of space?).

In point pattern analysis, the unit of support is the point, and the source of randomness is the location itself. Many other forms of data are also collected at points. For instance, when the census collects information on population, at its most basic, the information can be georeferenced to an address, that is, a point.

In numerous applications, however, data are not reported at their fundamental unit of support, but rather are aggregated to some other geometry, for instance an area. This is done for several reasons, including the privacy and confidentiality of the data. Instead of reporting individual-level information, the information is reported for zoning systems that often are devised without consideration to any underlying social, natural, or economic processes.

Census data, for instance, is reported at different levels of geography. In Canada, the smallest publicly available geography is called a Dissemination Area or DA. A DA in Canada contains a population between 400 and 700 persons. Thus, instead of reporting that one person (or more) are located at a point (i.e., an address), the census reports the population for the DA. Other data are aggregated in similar ways (income, residential status, etc.)

At the highest level of aggregation, national level statistics are reported, for instance Gross Domestic Product, or GDP. Economic production is not evenly distributed across space; however, the national GDP does not distinguish regional variations in this process.

Ideally, a data analyst would work with data in its most fundamental support. This is not alway possible, and therefore many techniques have been developed to work with data that have been agregated to zones.

When working with areas, it is less practical to identify the area with the coordinates (as we did with points). After all, areas will be composed of lines and reporting all the relevant coordinates is impractical. Sometimes the geometric centroids of the areas are used instead.

More commonly, areas are assigned an index or unique identifier, so that a region will typically consist of a set of \(n\) areas as follows: \[ R = A_1 \cup A_2 \cup A_3 \cup ...\cup A_n. \]

The above is read as “the Region R is the union of Areas 1 to n”.

Regions can have a set of \(k\) attributes or variables associated with them, for instance: \[ \textbf{X}_i=[x_{i1}, x_{i2}, x_{i3},...,x_{ik}] \]

These attributes will typically be counts (e.g., number of people in a DA), or some summary measure of the underlying data (e.g., mean commute time).

Processes and area data

Imagine that data on income by household were collected as follows:

df <- data.frame(x = c(0.3, 0.4, 0.5, 0.6, 0.7), y = c(0.1, 0.4, 0.2, 0.5, 0.3), Income = c(30000, 30000, 100000, 100000, 100000))

Households are geocoded as points with coordinates x and y, whereas income is in dollars.

Plot the income as points (hover over the points to see the attributes):

p <- ggplot(data = df, aes(x = x, y = y, color = Income)) + 
  geom_point(shape = 17, size = 5) +
  coord_fixed()
ggplotly(p)

The underlying process is one of income sorting, with lower incomes to the west, and higher incomes to the east. This could be due to a geographical feature of the landscape (for instance, an escarpment), or the distribution of the housing stock (with a neighborhood that has more expensive houses). These are examples of a variable that responds to a common environmental factor. As an alternative, people may display a preference towards being near others that are similar to them (this is called homophily). When this happens, the variable responds to itself in space.

The quality of similarity or disimilarity between neighboring observations of the same variable in space is called spatial autocorrelation. You will learn more about this later on.

Another reason why variables reported for areas could display similarities in space is as an consequence of the zoning system.

Suppose for a moment that the data above can only be reported at the zonal level, perhaps because of privacy and confidentiality concerns. Thanks to the great talent of the designers of the zoning system (or a felicitous coincidence!), the zoning system is such that it is consistent with the underlying process of sorting. The zones, therefore, are as follows:

zones1 <- data.frame(x1=c(0.2, 0.45), x2=c(0.45, 0.80), y1=c(0.0, 0.0), y2=c(0.6, 0.6), Zone_ID = c('1','2'))

If you add these zones to the plot:

p <- ggplot() + 
  geom_rect(data = zones1, mapping = aes(xmin = x1, xmax = x2, ymin = y1, ymax = y2, fill = Zone_ID), alpha = 0.3) + 
  geom_point(data = df, aes(x = x, y = y, color = Income), shape = 17, size = 5) +
  coord_fixed()
ggplotly(p)

What is the mean income in zone 1? What is the mean income in zone 2? Not only are the summary measures of income highly representative of the observations they describe, the two zones are also highly distinct.

Imagine now that for whatever reason (lack of prior knowledge of the process, convenience for data collection, etc.) the zones instead are as follows:

zones2 <- data.frame(x1=c(0.2, 0.55), x2=c(0.55, 0.80), y1=c(0.0, 0.0), y2=c(0.6, 0.6), Zone_ID = c('1','2'))

If you plot these zones:

p <- ggplot() + 
  geom_rect(data = zones2, mapping = aes(xmin = x1, xmax = x2, ymin = y1, ymax = y2, fill = Zone_ID), alpha = 0.3) + 
  geom_point(data = df, aes(x = x, y = y, color = Income), shape = 17, size = 5) +
  coord_fixed()
ggplotly(p)

What is now the mean income of zone 1? What is the mean income of zone 2? The observations have not changed, and the generating spatial process remains the same. You will notice, however, that the summary measures for the two zones are more similar in this case than they were when the zones more closely captured the underlying process.

Visualizing area data: choropleth maps

The initial step when working with spatial area data, perhaps, is to visualize the data.

Commonly, area data are visualized by means of choropleth maps. A choropleth map is a map of the polygons that form the areas in the region, each colored in a way to represent the value of an underlying variable.

Lets use ggplot2 to create a choropleth map of population in Hamilton. Notice that the fill color for the polygons is given by cutting the values of POPULATION in five equal segments. In other words, the colors represent zones in the bottom 20% of population, zones in the next 20%, and so on, so that the darkest zones are those with populations so large as to be in the top 20% of the population distribution:

ggplot(Hamilton_CT) + geom_sf(aes(fill = cut_number(Hamilton_CT$POPULATION, 5)), color = NA, size = 0.1) +
  scale_fill_brewer(palette = "YlOrRd") +
  coord_sf() +
  labs(fill = "Population")

Inspecting the map above, would you say that the distribution of population is random, or not random? If not random, what do you think might be an underlying process for the distribution of population.

Often, creating a choropleth map using the absolute value of a variable can be somewhat misleading. As seen in the map above, the zones with the largest population are also usually large zones. Any process that you might think of will be confounded by the size of the zones. For this reason, it is often more informative when creating a choropleth map to use a variable that is a rate, for instance population divided by area to give population density:

pop_den.map <- ggplot(Hamilton_CT) + 
  geom_sf(aes(fill = cut_number(Hamilton_CT$POP_DENSITY, 5)), color = "white", size = 0.1) +
  scale_fill_brewer(palette = "YlOrRd") +
  labs(fill = "Pop Density")
pop_den.map

It can be seen now that the population density is higher in the more central parts of Hamilton, Burlington, Dundas, etc. Does the map look random? If not, what might be an underlying process that explains the variations in population density in a city like Hamilton?

Other times, it is appropriate to standardize instead of by area, by what might be called the population at risk. For instance, lets say that we wanted to explore the distribution of the population of older adults (say, 65 and older). In this case, normalizing not by area, but by the total population, would remove the “size” effect, giving a proportion:

ggplot(Hamilton_CT) + 
  geom_sf(aes(fill = cut_number((Hamilton_CT$AGE_65_TO_69 +
                                   Hamilton_CT$AGE_70_TO_74 +
                                   Hamilton_CT$AGE_75_TO_79 +
                                   Hamilton_CT$AGE_80_TO_84 +
                                   Hamilton_CT$AGE_MORE_85) / Hamilton_CT$POPULATION, 5)),
          color = NA, 
          size = 0.1) +
  scale_fill_brewer(palette = "YlOrRd") +
  labs(fill = "Prop Age 65+")

Do you notice a pattern in the distribution of seniors in the Hamilton, CMA?

There are a few things to keep in mind when creating choroplet maps.

First, what classification scheme to use, with how many classes, and what colors?

The examples above were all created using a classification scheme based on the quintiles of the distribution. As noted above, these are obtained by dividing the sample into 5 equal parts to give bottom 20%, etc., of observations. The quintiles are a particular form of a statistical measure known as quantiles, of which the median is value obtained when the sample is divided in two equal sized parts. Other classification schemes may include the mean, standard deviation, and so on.

In terms of how many classes to use, often there is little point in using more than six or seven classes, because the human eye cannot distinguish color differences at a much higher resolution.

The colors are a matter of style, but there are coloring schemes that are colorblind safe (see here).

Secondly, when the zoning system is irregular (as opposed to, say, a raster), large zones can easily become dominant. In effect, much detail in the maps above is lost for small zones, whereas large zones, especially if similarly colored, may mislead the eye as to their relative frequency.

Another mapping technique, the cartogram, is meant to reduce the issues with small-large zones.

Visualizing area data: cartogram

A cartogram is a map where the size of the zones is adjusted so that instead of being the land area, it is proportional to some other variable of interest.

Lets illustrate the idea behind the cartogram here.

In the maps above, the zones are faithful to their geographical properties. Unfortunately, this obscured the relevance of small zones. A cartogram can be weighted by another variable, say for instance, the population. In this way, the size of the zones will depend on the total population.

Cartograms are implemented in R in the package cartogram.

CT_pop_cartogram <- cartogram_cont(as(Hamilton_CT, "Spatial"), "POPULATION")
Mean size error for iteration 1: 5.93989832705797
Mean size error for iteration 2: 4.22003636161907
Mean size error for iteration 3: 3.22484467962666
Mean size error for iteration 4: 2.67349944741397
Mean size error for iteration 5: 2.39730255727434
Mean size error for iteration 6: 2.27353151789255
Mean size error for iteration 7: 2.21619358572193
Mean size error for iteration 8: 2.1621830846263
Mean size error for iteration 9: 2.10302324046727
Mean size error for iteration 10: 1.97693517836395
Mean size error for iteration 11: 1.83404151346793
Mean size error for iteration 12: 1.68895026525741
Mean size error for iteration 13: 1.48501500944163
Mean size error for iteration 14: 1.32015982874586
Mean size error for iteration 15: 1.25319335368508

Notice that the value of the function cartogram (i.e., its output) is a SpatialPolygonsDataFrame. This object needs to be converted to an object of class sf if we wish to use ggplot2 to visualize it:

CT_pop_cartogram.t <- st_as_sf(CT_pop_cartogram)

Plotting the cartogram:

ggplot(CT_pop_cartogram.t) + 
  geom_sf(aes(fill = cut_number(Hamilton_CT$POPULATION, 5)), color = "white", size = 0.1) +
  scale_fill_brewer(palette = "YlOrRd") +
  labs(fill = "Population")

Notice how the size of the zones has been adjusted.

The cartogram can be combined with coloring schemes, as in choropleth maps:

CT_popden_cartogram <- cartogram(as(Hamilton_CT, "Spatial"), weight = "POP_DENSITY")

Please use cartogram_cont() instead of cartogram().

Mean size error for iteration 1: 29.038428707007
Mean size error for iteration 2: 26.8741045865025
Mean size error for iteration 3: 25.1188420421205
Mean size error for iteration 4: 23.62794892945
Mean size error for iteration 5: 22.3098917140505
Mean size error for iteration 6: 21.106667145772
Mean size error for iteration 7: 19.9815506326308
Mean size error for iteration 8: 18.9106321110581
Mean size error for iteration 9: 17.8793922806811
Mean size error for iteration 10: 16.8783214422446
Mean size error for iteration 11: 15.9023828765327
Mean size error for iteration 12: 14.9493939574685
Mean size error for iteration 13: 14.0197712378282
Mean size error for iteration 14: 13.115878344782
Mean size error for iteration 15: 12.2410530253921

Tidy and restore the data:

CT_popden_cartogram.t <- st_as_sf(CT_popden_cartogram)
pop_den.cartogram <- ggplot(CT_popden_cartogram.t) + 
  geom_sf(aes(fill = cut_number(Hamilton_CT$POP_DENSITY, 5)),color = "white", size = 0.1) +
  scale_fill_brewer(palette = "YlOrRd") +
  labs(fill = "Pop Density")
pop_den.cartogram

By combining a cartogram with choropleth mapping, it becomes easier to appreciate the way high population density is concentrated in the central parts of Hamilton, Burlington, etc.

grid.arrange(pop_den.map, pop_den.cartogram, nrow = 2)

This concludes this chapter.

LS0tDQp0aXRsZTogIjEwIEFyZWEgRGF0YSBJIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyBBcmVhIERhdGEgSQ0KDQoqTk9URSo6IFlvdSBjYW4gZG93bmxvYWQgdGhlIHNvdXJjZSBmaWxlcyBmb3IgdGhpcyBib29rIGZyb20gW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9wYWV6aGEvU3BhdGlhbC1TdGF0aXN0aWNzLUNvdXJzZSkuIFRoZSBzb3VyY2UgZmlsZXMgYXJlIGluIHRoZSBmb3JtYXQgb2YgUiBOb3RlYm9va3MuIE5vdGVib29rcyBhcmUgcHJldHR5IG5lYXQsIGJlY2F1c2UgdGhlIGFsbG93IHlvdSBleGVjdXRlIGNvZGUgd2l0aGluIHRoZSBub3RlYm9vaywgc28gdGhhdCB5b3UgY2FuIHdvcmsgaW50ZXJhY3RpdmVseSB3aXRoIHRoZSBub3Rlcy4gDQoNCkluIGxhc3QgZmV3IHByYWN0aWNlcy9zZXNzaW9ucywgeW91IGxlYXJuZWQgYWJvdXQgc3BhdGlhbCBwb2ludCBwYXR0ZXJucy4gVGhlIG5leHQgZmV3IHNlc3Npb25zIHdpbGwgY29uY2VudHJhdGUgb24gX2FyZWEgZGF0YV8uDQoNCklmIHlvdSB3aXNoIHRvIHdvcmsgaW50ZXJhY3RpdmVseSB3aXRoIHRoaXMgY2hhcHRlciB5b3Ugd2lsbCBuZWVkIHRoZSBmb2xsb3dpbmc6DQoNCiogQW4gUiBtYXJrZG93biBub3RlYm9vayB2ZXJzaW9uIG9mIHRoaXMgZG9jdW1lbnQgKHRoZSBzb3VyY2UgZmlsZSkuDQoNCiogQSBwYWNrYWdlIGNhbGxlZCBgZ2VvZzRnYTNgLg0KDQojIyBMZWFybmluZyBvYmplY3RpdmVzDQoNCkluIHRoaXMgcHJhY3RpY2UsIHlvdSB3aWxsIGxlYXJuOg0KDQoxLiBBIGZvcm1hbCBkZWZpbml0aW9uIG9mIGFyZWEgZGF0YS4NCjIuIFByb2Nlc3NlcyBhbmQgYXJlYSBkYXRhLg0KMy4gVmlzdWFsaXppbmcgYXJlYSBkYXRhOiBDaG9yb3BsZXRoIG1hcHMuDQo0LiBWaXN1YWxpemluZyBhcmVhIGRhdGE6IENhcnRvZ3JhbXMuDQoNCiMjIFN1Z2dlc3RlZCByZWFkaW5nDQoNCk8nU3VsbGl2YW4gRCBhbmQgVW53aW4gRCAoMjAxMCkgR2VvZ3JhcGhpYyBJbmZvcm1hdGlvbiBBbmFseXNpcywgMm5kIEVkaXRpb24sIENoYXB0ZXIgNy4gSm9obiBXaWxleSAmIFNvbnM6IE5ldyBKZXJzZXkuDQoNCiMjIFByZWxpbWluYXJpZXMNCg0KQXMgdXN1YWwsIGl0IGlzIGdvb2QgcHJhY3RpY2UgdG8gY2xlYXIgdGhlIHdvcmtpbmcgc3BhY2UgdG8gbWFrZSBzdXJlIHRoYXQgeW91IGRvIG5vdCBoYXZlIGV4dHJhbmVvdXMgaXRlbXMgdGhlcmUgd2hlbiB5b3UgYmVnaW4geW91ciB3b3JrLiBUaGUgY29tbWFuZCBpbiBSIHRvIGNsZWFyIHRoZSB3b3Jrc3BhY2UgaXMgYHJtYCAoZm9yICJyZW1vdmUiKSwgZm9sbG93ZWQgYnkgYSBsaXN0IG9mIGl0ZW1zIHRvIGJlIHJlbW92ZWQuIFRvIGNsZWFyIHRoZSB3b3Jrc3BhY2UgZnJvbSBfYWxsXyBvYmplY3RzLCBkbyB0aGUgZm9sbG93aW5nOg0KYGBge3J9DQpybShsaXN0ID0gbHMoKSkNCmBgYA0KDQpOb3RlIHRoYXQgYGxzKClgIGxpc3RzIGFsbCBvYmplY3RzIGN1cnJlbnRseSBvbiB0aGUgd29yc3BhY2UuDQoNCkxvYWQgdGhlIGxpYnJhcmllcyB5b3Ugd2lsbCB1c2UgaW4gdGhpcyBhY3Rpdml0eToNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQojbGlicmFyeShyZ2RhbCkNCiNsaWJyYXJ5KGJyb29tKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KGNhcnRvZ3JhbSkNCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KbGlicmFyeShnZW9nNGdhMykNCmBgYA0KDQpSZWFkIHRoZSBkYXRhIHVzZWQgaW4gdGhpcyBjaGFwdGVyLg0KYGBge3J9DQpkYXRhKCJIYW1pbHRvbl9DVCIpDQpgYGANCg0KVGhlIGRhdGFmcmFtZSBpbmNsdWRlcyB0aGUgc3BhdGlhbCBpbmZvcm1hdGlvbiBmb3IgdGhlIGNlbnN1cyB0cmFjdHMgaW4gdGhlIEhhbWlsdG9uIENlbnN1cyBNZXRyb3BvbGl0YW4gQXJlYSBpbiBDYW5hZGEgKGFzIHBvbHlnb25zKSwgYW5kIGEgc2VyaWVzIG9mIGRlbW9ncmFwaGljIHZhcmlhYmxlcyBmcm9tIHRoZSAyMDExIENlbnN1cyBvZiBDYW5hZGEuDQoNCllvdSBjYW4gcXVpY2tseSB2ZXJpZnkgdGhlIGNvbnRlbnRzIG9mIHRoZSBkYXRhZnJhbWUgYnkgbWVhbnMgb2YgYHN1bW1hcnlgOg0KYGBge3J9DQpzdW1tYXJ5KEhhbWlsdG9uX0NUKQ0KYGBgDQoNCiMjIEFyZWEgZGF0YQ0KDQpFdmVyeSBwaGVub21lbmEgY2FuIGJlIG1lYXN1cmVkIGF0IGEgbG9jYXRpb24gKGFzayB5b3Vyc2VsZiwgd2hhdCBleGlzdHMgb3V0c2lkZSBvZiBzcGFjZT8pLg0KDQpJbiBwb2ludCBwYXR0ZXJuIGFuYWx5c2lzLCB0aGUgX3VuaXQgb2Ygc3VwcG9ydF8gaXMgdGhlIHBvaW50LCBhbmQgdGhlIHNvdXJjZSBvZiByYW5kb21uZXNzIGlzIHRoZSBsb2NhdGlvbiBpdHNlbGYuIE1hbnkgb3RoZXIgZm9ybXMgb2YgZGF0YSBhcmUgYWxzbyBjb2xsZWN0ZWQgYXQgcG9pbnRzLiBGb3IgaW5zdGFuY2UsIHdoZW4gdGhlIGNlbnN1cyBjb2xsZWN0cyBpbmZvcm1hdGlvbiBvbiBwb3B1bGF0aW9uLCBhdCBpdHMgbW9zdCBiYXNpYywgdGhlIGluZm9ybWF0aW9uIGNhbiBiZSBnZW9yZWZlcmVuY2VkIHRvIGFuIGFkZHJlc3MsIHRoYXQgaXMsIGEgcG9pbnQuDQoNCkluIG51bWVyb3VzIGFwcGxpY2F0aW9ucywgaG93ZXZlciwgZGF0YSBhcmUgbm90IHJlcG9ydGVkIGF0IHRoZWlyIGZ1bmRhbWVudGFsIHVuaXQgb2Ygc3VwcG9ydCwgYnV0IHJhdGhlciBhcmUgYWdncmVnYXRlZCB0byBzb21lIG90aGVyIGdlb21ldHJ5LCBmb3IgaW5zdGFuY2UgYW4gYXJlYS4gVGhpcyBpcyBkb25lIGZvciBzZXZlcmFsIHJlYXNvbnMsIGluY2x1ZGluZyB0aGUgcHJpdmFjeSBhbmQgY29uZmlkZW50aWFsaXR5IG9mIHRoZSBkYXRhLiBJbnN0ZWFkIG9mIHJlcG9ydGluZyBpbmRpdmlkdWFsLWxldmVsIGluZm9ybWF0aW9uLCB0aGUgaW5mb3JtYXRpb24gaXMgcmVwb3J0ZWQgZm9yIHpvbmluZyBzeXN0ZW1zIHRoYXQgb2Z0ZW4gYXJlIGRldmlzZWQgd2l0aG91dCBjb25zaWRlcmF0aW9uIHRvIGFueSB1bmRlcmx5aW5nIHNvY2lhbCwgbmF0dXJhbCwgb3IgZWNvbm9taWMgcHJvY2Vzc2VzLg0KDQpDZW5zdXMgZGF0YSwgZm9yIGluc3RhbmNlLCBpcyByZXBvcnRlZCBhdCBkaWZmZXJlbnQgbGV2ZWxzIG9mIGdlb2dyYXBoeS4gSW4gQ2FuYWRhLCB0aGUgc21hbGxlc3QgcHVibGljbHkgYXZhaWxhYmxlIGdlb2dyYXBoeSBpcyBjYWxsZWQgYSBfRGlzc2VtaW5hdGlvbiBBcmVhXyBvciBbREFdKGh0dHA6Ly93d3cxMi5zdGF0Y2FuLmdjLmNhL2NlbnN1cy1yZWNlbnNlbWVudC8yMDExL3JlZi9kaWN0L2dlbzAyMS1lbmcuY2ZtKS4gQSBEQSBpbiBDYW5hZGEgY29udGFpbnMgYSBwb3B1bGF0aW9uIGJldHdlZW4gNDAwIGFuZCA3MDAgcGVyc29ucy4gVGh1cywgaW5zdGVhZCBvZiByZXBvcnRpbmcgdGhhdCBvbmUgcGVyc29uIChvciBtb3JlKSBhcmUgbG9jYXRlZCBhdCBhIHBvaW50IChpLmUuLCBhbiBhZGRyZXNzKSwgdGhlIGNlbnN1cyByZXBvcnRzIHRoZSBwb3B1bGF0aW9uIGZvciB0aGUgREEuIE90aGVyIGRhdGEgYXJlIGFnZ3JlZ2F0ZWQgaW4gc2ltaWxhciB3YXlzIChpbmNvbWUsIHJlc2lkZW50aWFsIHN0YXR1cywgZXRjLikNCg0KQXQgdGhlIGhpZ2hlc3QgbGV2ZWwgb2YgYWdncmVnYXRpb24sIG5hdGlvbmFsIGxldmVsIHN0YXRpc3RpY3MgYXJlIHJlcG9ydGVkLCBmb3IgaW5zdGFuY2UgR3Jvc3MgRG9tZXN0aWMgUHJvZHVjdCwgb3IgR0RQLiBFY29ub21pYyBwcm9kdWN0aW9uIGlzIG5vdCBldmVubHkgZGlzdHJpYnV0ZWQgYWNyb3NzIHNwYWNlOyBob3dldmVyLCB0aGUgbmF0aW9uYWwgR0RQIGRvZXMgbm90IGRpc3Rpbmd1aXNoIHJlZ2lvbmFsIHZhcmlhdGlvbnMgaW4gdGhpcyBwcm9jZXNzLg0KDQpJZGVhbGx5LCBhIGRhdGEgYW5hbHlzdCB3b3VsZCB3b3JrIHdpdGggZGF0YSBpbiBpdHMgbW9zdCBmdW5kYW1lbnRhbCBzdXBwb3J0LiBUaGlzIGlzIG5vdCBhbHdheSBwb3NzaWJsZSwgYW5kIHRoZXJlZm9yZSBtYW55IHRlY2huaXF1ZXMgaGF2ZSBiZWVuIGRldmVsb3BlZCB0byB3b3JrIHdpdGggZGF0YSB0aGF0IGhhdmUgYmVlbiBhZ3JlZ2F0ZWQgdG8gem9uZXMuDQoNCldoZW4gd29ya2luZyB3aXRoIGFyZWFzLCBpdCBpcyBsZXNzIHByYWN0aWNhbCB0byBpZGVudGlmeSB0aGUgYXJlYSB3aXRoIHRoZSBjb29yZGluYXRlcyAoYXMgd2UgZGlkIHdpdGggcG9pbnRzKS4gQWZ0ZXIgYWxsLCBhcmVhcyB3aWxsIGJlIGNvbXBvc2VkIG9mIGxpbmVzIGFuZCByZXBvcnRpbmcgYWxsIHRoZSByZWxldmFudCBjb29yZGluYXRlcyBpcyBpbXByYWN0aWNhbC4gU29tZXRpbWVzIHRoZSBnZW9tZXRyaWMgY2VudHJvaWRzIG9mIHRoZSBhcmVhcyBhcmUgdXNlZCBpbnN0ZWFkLg0KDQpNb3JlIGNvbW1vbmx5LCBhcmVhcyBhcmUgYXNzaWduZWQgYW4gaW5kZXggb3IgdW5pcXVlIGlkZW50aWZpZXIsIHNvIHRoYXQgYSByZWdpb24gd2lsbCB0eXBpY2FsbHkgY29uc2lzdCBvZiBhIHNldCBvZiAkbiQgYXJlYXMgYXMgZm9sbG93czoNCiQkDQpSID0gQV8xIFxjdXAgQV8yIFxjdXAgQV8zIFxjdXAgLi4uXGN1cCBBX24uDQokJA0KDQpUaGUgYWJvdmUgaXMgcmVhZCBhcyAidGhlIFJlZ2lvbiBSIGlzIHRoZSB1bmlvbiBvZiBBcmVhcyAxIHRvIG4iLg0KDQpSZWdpb25zIGNhbiBoYXZlIGEgc2V0IG9mICRrJCBhdHRyaWJ1dGVzIG9yIHZhcmlhYmxlcyBhc3NvY2lhdGVkIHdpdGggdGhlbSwgZm9yIGluc3RhbmNlOg0KJCQNClx0ZXh0YmZ7WH1faT1beF97aTF9LCB4X3tpMn0sIHhfe2kzfSwuLi4seF97aWt9XQ0KJCQNCg0KVGhlc2UgYXR0cmlidXRlcyB3aWxsIHR5cGljYWxseSBiZSBjb3VudHMgKGUuZy4sIG51bWJlciBvZiBwZW9wbGUgaW4gYSBEQSksIG9yIHNvbWUgc3VtbWFyeSBtZWFzdXJlIG9mIHRoZSB1bmRlcmx5aW5nIGRhdGEgKGUuZy4sIG1lYW4gY29tbXV0ZSB0aW1lKS4NCg0KIyMgUHJvY2Vzc2VzIGFuZCBhcmVhIGRhdGENCg0KSW1hZ2luZSB0aGF0IGRhdGEgb24gaW5jb21lIGJ5IGhvdXNlaG9sZCB3ZXJlIGNvbGxlY3RlZCBhcyBmb2xsb3dzOg0KYGBge3J9DQpkZiA8LSBkYXRhLmZyYW1lKHggPSBjKDAuMywgMC40LCAwLjUsIDAuNiwgMC43KSwgeSA9IGMoMC4xLCAwLjQsIDAuMiwgMC41LCAwLjMpLCBJbmNvbWUgPSBjKDMwMDAwLCAzMDAwMCwgMTAwMDAwLCAxMDAwMDAsIDEwMDAwMCkpDQpgYGANCg0KSG91c2Vob2xkcyBhcmUgZ2VvY29kZWQgYXMgcG9pbnRzIHdpdGggY29vcmRpbmF0ZXMgYHhgIGFuZCBgeWAsIHdoZXJlYXMgaW5jb21lIGlzIGluIGRvbGxhcnMuDQoNClBsb3QgdGhlIGluY29tZSBhcyBwb2ludHMgKGhvdmVyIG92ZXIgdGhlIHBvaW50cyB0byBzZWUgdGhlIGF0dHJpYnV0ZXMpOg0KYGBge3J9DQpwIDwtIGdncGxvdChkYXRhID0gZGYsIGFlcyh4ID0geCwgeSA9IHksIGNvbG9yID0gSW5jb21lKSkgKyANCiAgZ2VvbV9wb2ludChzaGFwZSA9IDE3LCBzaXplID0gNSkgKw0KICBjb29yZF9maXhlZCgpDQpnZ3Bsb3RseShwKQ0KYGBgDQoNClRoZSB1bmRlcmx5aW5nIHByb2Nlc3MgaXMgb25lIG9mIGluY29tZSBzb3J0aW5nLCB3aXRoIGxvd2VyIGluY29tZXMgdG8gdGhlIHdlc3QsIGFuZCBoaWdoZXIgaW5jb21lcyB0byB0aGUgZWFzdC4gVGhpcyBjb3VsZCBiZSBkdWUgdG8gYSBnZW9ncmFwaGljYWwgZmVhdHVyZSBvZiB0aGUgbGFuZHNjYXBlIChmb3IgaW5zdGFuY2UsIGFuIGVzY2FycG1lbnQpLCBvciB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBob3VzaW5nIHN0b2NrICh3aXRoIGEgbmVpZ2hib3Job29kIHRoYXQgaGFzIG1vcmUgZXhwZW5zaXZlIGhvdXNlcykuIFRoZXNlIGFyZSBleGFtcGxlcyBvZiBhIHZhcmlhYmxlIHRoYXQgcmVzcG9uZHMgdG8gYSBjb21tb24gZW52aXJvbm1lbnRhbCBmYWN0b3IuIEFzIGFuIGFsdGVybmF0aXZlLCBwZW9wbGUgbWF5IGRpc3BsYXkgYSBwcmVmZXJlbmNlIHRvd2FyZHMgYmVpbmcgbmVhciBvdGhlcnMgdGhhdCBhcmUgc2ltaWxhciB0byB0aGVtICh0aGlzIGlzIGNhbGxlZCBob21vcGhpbHkpLiBXaGVuIHRoaXMgaGFwcGVucywgdGhlIHZhcmlhYmxlIHJlc3BvbmRzIHRvIGl0c2VsZiBpbiBzcGFjZS4NCg0KVGhlIHF1YWxpdHkgb2Ygc2ltaWxhcml0eSBvciBkaXNpbWlsYXJpdHkgYmV0d2VlbiBuZWlnaGJvcmluZyBvYnNlcnZhdGlvbnMgb2YgdGhlIHNhbWUgdmFyaWFibGUgaW4gc3BhY2UgaXMgY2FsbGVkIF9zcGF0aWFsIGF1dG9jb3JyZWxhdGlvbl8uIFlvdSB3aWxsIGxlYXJuIG1vcmUgYWJvdXQgdGhpcyBsYXRlciBvbi4NCg0KQW5vdGhlciByZWFzb24gd2h5IHZhcmlhYmxlcyByZXBvcnRlZCBmb3IgYXJlYXMgY291bGQgZGlzcGxheSBzaW1pbGFyaXRpZXMgaW4gc3BhY2UgaXMgYXMgYW4gY29uc2VxdWVuY2Ugb2YgdGhlIHpvbmluZyBzeXN0ZW0uDQoNClN1cHBvc2UgZm9yIGEgbW9tZW50IHRoYXQgdGhlIGRhdGEgYWJvdmUgY2FuIG9ubHkgYmUgcmVwb3J0ZWQgYXQgdGhlIHpvbmFsIGxldmVsLCBwZXJoYXBzIGJlY2F1c2Ugb2YgcHJpdmFjeSBhbmQgY29uZmlkZW50aWFsaXR5IGNvbmNlcm5zLiBUaGFua3MgdG8gdGhlIGdyZWF0IHRhbGVudCBvZiB0aGUgZGVzaWduZXJzIG9mIHRoZSB6b25pbmcgc3lzdGVtIChvciBhIGZlbGljaXRvdXMgY29pbmNpZGVuY2UhKSwgdGhlIHpvbmluZyBzeXN0ZW0gaXMgc3VjaCB0aGF0IGl0IGlzIGNvbnNpc3RlbnQgd2l0aCB0aGUgdW5kZXJseWluZyBwcm9jZXNzIG9mIHNvcnRpbmcuIFRoZSB6b25lcywgdGhlcmVmb3JlLCBhcmUgYXMgZm9sbG93czoNCmBgYHtyfQ0Kem9uZXMxIDwtIGRhdGEuZnJhbWUoeDE9YygwLjIsIDAuNDUpLCB4Mj1jKDAuNDUsIDAuODApLCB5MT1jKDAuMCwgMC4wKSwgeTI9YygwLjYsIDAuNiksIFpvbmVfSUQgPSBjKCcxJywnMicpKQ0KYGBgDQoNCklmIHlvdSBhZGQgdGhlc2Ugem9uZXMgdG8gdGhlIHBsb3Q6DQpgYGB7cn0NCnAgPC0gZ2dwbG90KCkgKyANCiAgZ2VvbV9yZWN0KGRhdGEgPSB6b25lczEsIG1hcHBpbmcgPSBhZXMoeG1pbiA9IHgxLCB4bWF4ID0geDIsIHltaW4gPSB5MSwgeW1heCA9IHkyLCBmaWxsID0gWm9uZV9JRCksIGFscGhhID0gMC4zKSArIA0KICBnZW9tX3BvaW50KGRhdGEgPSBkZiwgYWVzKHggPSB4LCB5ID0geSwgY29sb3IgPSBJbmNvbWUpLCBzaGFwZSA9IDE3LCBzaXplID0gNSkgKw0KICBjb29yZF9maXhlZCgpDQpnZ3Bsb3RseShwKQ0KYGBgDQoNCldoYXQgaXMgdGhlIG1lYW4gaW5jb21lIGluIHpvbmUgMT8gV2hhdCBpcyB0aGUgbWVhbiBpbmNvbWUgaW4gem9uZSAyPyBOb3Qgb25seSBhcmUgdGhlIHN1bW1hcnkgbWVhc3VyZXMgb2YgaW5jb21lIGhpZ2hseSByZXByZXNlbnRhdGl2ZSBvZiB0aGUgb2JzZXJ2YXRpb25zIHRoZXkgZGVzY3JpYmUsIHRoZSB0d28gem9uZXMgYXJlIGFsc28gaGlnaGx5IGRpc3RpbmN0Lg0KDQpJbWFnaW5lIG5vdyB0aGF0IGZvciB3aGF0ZXZlciByZWFzb24gKGxhY2sgb2YgcHJpb3Iga25vd2xlZGdlIG9mIHRoZSBwcm9jZXNzLCBjb252ZW5pZW5jZSBmb3IgZGF0YSBjb2xsZWN0aW9uLCBldGMuKSB0aGUgem9uZXMgaW5zdGVhZCBhcmUgYXMgZm9sbG93czoNCmBgYHtyfQ0Kem9uZXMyIDwtIGRhdGEuZnJhbWUoeDE9YygwLjIsIDAuNTUpLCB4Mj1jKDAuNTUsIDAuODApLCB5MT1jKDAuMCwgMC4wKSwgeTI9YygwLjYsIDAuNiksIFpvbmVfSUQgPSBjKCcxJywnMicpKQ0KYGBgDQoNCklmIHlvdSBwbG90IHRoZXNlIHpvbmVzOg0KYGBge3J9DQpwIDwtIGdncGxvdCgpICsgDQogIGdlb21fcmVjdChkYXRhID0gem9uZXMyLCBtYXBwaW5nID0gYWVzKHhtaW4gPSB4MSwgeG1heCA9IHgyLCB5bWluID0geTEsIHltYXggPSB5MiwgZmlsbCA9IFpvbmVfSUQpLCBhbHBoYSA9IDAuMykgKyANCiAgZ2VvbV9wb2ludChkYXRhID0gZGYsIGFlcyh4ID0geCwgeSA9IHksIGNvbG9yID0gSW5jb21lKSwgc2hhcGUgPSAxNywgc2l6ZSA9IDUpICsNCiAgY29vcmRfZml4ZWQoKQ0KZ2dwbG90bHkocCkNCmBgYA0KDQpXaGF0IGlzIG5vdyB0aGUgbWVhbiBpbmNvbWUgb2Ygem9uZSAxPyBXaGF0IGlzIHRoZSBtZWFuIGluY29tZSBvZiB6b25lIDI/IFRoZSBvYnNlcnZhdGlvbnMgaGF2ZSBub3QgY2hhbmdlZCwgYW5kIHRoZSBnZW5lcmF0aW5nIHNwYXRpYWwgcHJvY2VzcyByZW1haW5zIHRoZSBzYW1lLiBZb3Ugd2lsbCBub3RpY2UsIGhvd2V2ZXIsIHRoYXQgdGhlIHN1bW1hcnkgbWVhc3VyZXMgZm9yIHRoZSB0d28gem9uZXMgYXJlIG1vcmUgc2ltaWxhciBpbiB0aGlzIGNhc2UgdGhhbiB0aGV5IHdlcmUgd2hlbiB0aGUgem9uZXMgbW9yZSBjbG9zZWx5IGNhcHR1cmVkIHRoZSB1bmRlcmx5aW5nIHByb2Nlc3MuDQoNCiMjIFZpc3VhbGl6aW5nIGFyZWEgZGF0YTogY2hvcm9wbGV0aCBtYXBzDQoNClRoZSBpbml0aWFsIHN0ZXAgd2hlbiB3b3JraW5nIHdpdGggc3BhdGlhbCBhcmVhIGRhdGEsIHBlcmhhcHMsIGlzIHRvIHZpc3VhbGl6ZSB0aGUgZGF0YS4NCg0KQ29tbW9ubHksIGFyZWEgZGF0YSBhcmUgdmlzdWFsaXplZCBieSBtZWFucyBvZiBjaG9yb3BsZXRoIG1hcHMuIEEgY2hvcm9wbGV0aCBtYXAgaXMgYSBtYXAgb2YgdGhlIHBvbHlnb25zIHRoYXQgZm9ybSB0aGUgYXJlYXMgaW4gdGhlIHJlZ2lvbiwgZWFjaCBjb2xvcmVkIGluIGEgd2F5IHRvIHJlcHJlc2VudCB0aGUgdmFsdWUgb2YgYW4gdW5kZXJseWluZyB2YXJpYWJsZS4gDQoNCkxldHMgdXNlIGBnZ3Bsb3QyYCB0byBjcmVhdGUgYSBjaG9yb3BsZXRoIG1hcCBvZiBwb3B1bGF0aW9uIGluIEhhbWlsdG9uLiBOb3RpY2UgdGhhdCB0aGUgZmlsbCBjb2xvciBmb3IgdGhlIHBvbHlnb25zIGlzIGdpdmVuIGJ5IGN1dHRpbmcgdGhlIHZhbHVlcyBvZiBgUE9QVUxBVElPTmAgaW4gZml2ZSBlcXVhbCBzZWdtZW50cy4gSW4gb3RoZXIgd29yZHMsIHRoZSBjb2xvcnMgcmVwcmVzZW50IHpvbmVzIGluIHRoZSBib3R0b20gMjAlIG9mIHBvcHVsYXRpb24sIHpvbmVzIGluIHRoZSBuZXh0IDIwJSwgYW5kIHNvIG9uLCBzbyB0aGF0IHRoZSBkYXJrZXN0IHpvbmVzIGFyZSB0aG9zZSB3aXRoIHBvcHVsYXRpb25zIHNvIGxhcmdlIGFzIHRvIGJlIGluIHRoZSB0b3AgMjAlIG9mIHRoZSBwb3B1bGF0aW9uIGRpc3RyaWJ1dGlvbjoNCmBgYHtyfQ0KZ2dwbG90KEhhbWlsdG9uX0NUKSArIGdlb21fc2YoYWVzKGZpbGwgPSBjdXRfbnVtYmVyKEhhbWlsdG9uX0NUJFBPUFVMQVRJT04sIDUpKSwgY29sb3IgPSBOQSwgc2l6ZSA9IDAuMSkgKw0KICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIllsT3JSZCIpICsNCiAgY29vcmRfc2YoKSArDQogIGxhYnMoZmlsbCA9ICJQb3B1bGF0aW9uIikNCmBgYCAgDQoNCkluc3BlY3RpbmcgdGhlIG1hcCBhYm92ZSwgd291bGQgeW91IHNheSB0aGF0IHRoZSBkaXN0cmlidXRpb24gb2YgcG9wdWxhdGlvbiBpcyByYW5kb20sIG9yIG5vdCByYW5kb20/IElmIG5vdCByYW5kb20sIHdoYXQgZG8geW91IHRoaW5rIG1pZ2h0IGJlIGFuIHVuZGVybHlpbmcgcHJvY2VzcyBmb3IgdGhlIGRpc3RyaWJ1dGlvbiBvZiBwb3B1bGF0aW9uLg0KDQpPZnRlbiwgY3JlYXRpbmcgYSBjaG9yb3BsZXRoIG1hcCB1c2luZyB0aGUgYWJzb2x1dGUgdmFsdWUgb2YgYSB2YXJpYWJsZSBjYW4gYmUgc29tZXdoYXQgbWlzbGVhZGluZy4gQXMgc2VlbiBpbiB0aGUgbWFwIGFib3ZlLCB0aGUgem9uZXMgd2l0aCB0aGUgbGFyZ2VzdCBwb3B1bGF0aW9uIGFyZSBhbHNvIHVzdWFsbHkgbGFyZ2Ugem9uZXMuIEFueSBwcm9jZXNzIHRoYXQgeW91IG1pZ2h0IHRoaW5rIG9mIHdpbGwgYmUgY29uZm91bmRlZCBieSB0aGUgc2l6ZSBvZiB0aGUgem9uZXMuIEZvciB0aGlzIHJlYXNvbiwgaXQgaXMgb2Z0ZW4gbW9yZSBpbmZvcm1hdGl2ZSB3aGVuIGNyZWF0aW5nIGEgY2hvcm9wbGV0aCBtYXAgdG8gdXNlIGEgdmFyaWFibGUgdGhhdCBpcyBhIHJhdGUsIGZvciBpbnN0YW5jZSBwb3B1bGF0aW9uIGRpdmlkZWQgYnkgYXJlYSB0byBnaXZlIHBvcHVsYXRpb24gZGVuc2l0eToNCmBgYHtyfQ0KcG9wX2Rlbi5tYXAgPC0gZ2dwbG90KEhhbWlsdG9uX0NUKSArIA0KICBnZW9tX3NmKGFlcyhmaWxsID0gY3V0X251bWJlcihIYW1pbHRvbl9DVCRQT1BfREVOU0lUWSwgNSkpLCBjb2xvciA9ICJ3aGl0ZSIsIHNpemUgPSAwLjEpICsNCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJZbE9yUmQiKSArDQogIGxhYnMoZmlsbCA9ICJQb3AgRGVuc2l0eSIpDQpwb3BfZGVuLm1hcA0KYGBgDQoNCkl0IGNhbiBiZSBzZWVuIG5vdyB0aGF0IHRoZSBwb3B1bGF0aW9uIGRlbnNpdHkgaXMgaGlnaGVyIGluIHRoZSBtb3JlIGNlbnRyYWwgcGFydHMgb2YgSGFtaWx0b24sIEJ1cmxpbmd0b24sIER1bmRhcywgZXRjLiBEb2VzIHRoZSBtYXAgbG9vayByYW5kb20/IElmIG5vdCwgd2hhdCBtaWdodCBiZSBhbiB1bmRlcmx5aW5nIHByb2Nlc3MgdGhhdCBleHBsYWlucyB0aGUgdmFyaWF0aW9ucyBpbiBwb3B1bGF0aW9uIGRlbnNpdHkgaW4gYSBjaXR5IGxpa2UgSGFtaWx0b24/DQoNCk90aGVyIHRpbWVzLCBpdCBpcyBhcHByb3ByaWF0ZSB0byBzdGFuZGFyZGl6ZSBpbnN0ZWFkIG9mIGJ5IGFyZWEsIGJ5IHdoYXQgbWlnaHQgYmUgY2FsbGVkIHRoZSBfcG9wdWxhdGlvbiBhdCByaXNrXy4gRm9yIGluc3RhbmNlLCBsZXRzIHNheSB0aGF0IHdlIHdhbnRlZCB0byBleHBsb3JlIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHBvcHVsYXRpb24gb2Ygb2xkZXIgYWR1bHRzIChzYXksIDY1IGFuZCBvbGRlcikuIEluIHRoaXMgY2FzZSwgbm9ybWFsaXppbmcgbm90IGJ5IGFyZWEsIGJ1dCBieSB0aGUgdG90YWwgcG9wdWxhdGlvbiwgd291bGQgcmVtb3ZlIHRoZSAic2l6ZSIgZWZmZWN0LCBnaXZpbmcgYSBwcm9wb3J0aW9uOg0KYGBge3J9DQpnZ3Bsb3QoSGFtaWx0b25fQ1QpICsgDQogIGdlb21fc2YoYWVzKGZpbGwgPSBjdXRfbnVtYmVyKChIYW1pbHRvbl9DVCRBR0VfNjVfVE9fNjkgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBIYW1pbHRvbl9DVCRBR0VfNzBfVE9fNzQgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBIYW1pbHRvbl9DVCRBR0VfNzVfVE9fNzkgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBIYW1pbHRvbl9DVCRBR0VfODBfVE9fODQgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBIYW1pbHRvbl9DVCRBR0VfTU9SRV84NSkgLyBIYW1pbHRvbl9DVCRQT1BVTEFUSU9OLCA1KSksDQogICAgICAgICAgY29sb3IgPSBOQSwgDQogICAgICAgICAgc2l6ZSA9IDAuMSkgKw0KICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIllsT3JSZCIpICsNCiAgbGFicyhmaWxsID0gIlByb3AgQWdlIDY1KyIpDQpgYGANCg0KRG8geW91IG5vdGljZSBhIHBhdHRlcm4gaW4gdGhlIGRpc3RyaWJ1dGlvbiBvZiBzZW5pb3JzIGluIHRoZSBIYW1pbHRvbiwgQ01BPw0KDQpUaGVyZSBhcmUgYSBmZXcgdGhpbmdzIHRvIGtlZXAgaW4gbWluZCB3aGVuIGNyZWF0aW5nIGNob3JvcGxldCBtYXBzLg0KDQpGaXJzdCwgd2hhdCBjbGFzc2lmaWNhdGlvbiBzY2hlbWUgdG8gdXNlLCB3aXRoIGhvdyBtYW55IGNsYXNzZXMsIGFuZCB3aGF0IGNvbG9ycz8gDQoNClRoZSBleGFtcGxlcyBhYm92ZSB3ZXJlIGFsbCBjcmVhdGVkIHVzaW5nIGEgY2xhc3NpZmljYXRpb24gc2NoZW1lIGJhc2VkIG9uIHRoZSBfcXVpbnRpbGVzXyBvZiB0aGUgZGlzdHJpYnV0aW9uLiBBcyBub3RlZCBhYm92ZSwgdGhlc2UgYXJlIG9idGFpbmVkIGJ5IGRpdmlkaW5nIHRoZSBzYW1wbGUgaW50byA1IGVxdWFsIHBhcnRzIHRvIGdpdmUgYm90dG9tIDIwJSwgZXRjLiwgb2Ygb2JzZXJ2YXRpb25zLiBUaGUgcXVpbnRpbGVzIGFyZSBhIHBhcnRpY3VsYXIgZm9ybSBvZiBhIHN0YXRpc3RpY2FsIG1lYXN1cmUga25vd24gYXMgX3F1YW50aWxlc18sIG9mIHdoaWNoIHRoZSBfbWVkaWFuXyBpcyB2YWx1ZSBvYnRhaW5lZCB3aGVuIHRoZSBzYW1wbGUgaXMgZGl2aWRlZCBpbiB0d28gZXF1YWwgc2l6ZWQgcGFydHMuIE90aGVyIGNsYXNzaWZpY2F0aW9uIHNjaGVtZXMgbWF5IGluY2x1ZGUgdGhlIG1lYW4sIHN0YW5kYXJkIGRldmlhdGlvbiwgYW5kIHNvIG9uLiANCg0KSW4gdGVybXMgb2YgaG93IG1hbnkgY2xhc3NlcyB0byB1c2UsIG9mdGVuIHRoZXJlIGlzIGxpdHRsZSBwb2ludCBpbiB1c2luZyBtb3JlIHRoYW4gc2l4IG9yIHNldmVuIGNsYXNzZXMsIGJlY2F1c2UgdGhlIGh1bWFuIGV5ZSBjYW5ub3QgZGlzdGluZ3Vpc2ggY29sb3IgZGlmZmVyZW5jZXMgYXQgYSBtdWNoIGhpZ2hlciByZXNvbHV0aW9uLg0KDQpUaGUgY29sb3JzIGFyZSBhIG1hdHRlciBvZiBzdHlsZSwgYnV0IHRoZXJlIGFyZSBjb2xvcmluZyBzY2hlbWVzIHRoYXQgYXJlIGNvbG9yYmxpbmQgc2FmZSAoc2VlIFtoZXJlXShodHRwOi8vY29sb3JicmV3ZXIyLm9yZy8jdHlwZT1zZXF1ZW50aWFsJnNjaGVtZT1CdUduJm49MykpLg0KDQpTZWNvbmRseSwgd2hlbiB0aGUgem9uaW5nIHN5c3RlbSBpcyBpcnJlZ3VsYXIgKGFzIG9wcG9zZWQgdG8sIHNheSwgYSByYXN0ZXIpLCBsYXJnZSB6b25lcyBjYW4gZWFzaWx5IGJlY29tZSBkb21pbmFudC4gSW4gZWZmZWN0LCBtdWNoIGRldGFpbCBpbiB0aGUgbWFwcyBhYm92ZSBpcyBsb3N0IGZvciBzbWFsbCB6b25lcywgd2hlcmVhcyBsYXJnZSB6b25lcywgZXNwZWNpYWxseSBpZiBzaW1pbGFybHkgY29sb3JlZCwgbWF5IG1pc2xlYWQgdGhlIGV5ZSBhcyB0byB0aGVpciByZWxhdGl2ZSBmcmVxdWVuY3kuDQoNCkFub3RoZXIgbWFwcGluZyB0ZWNobmlxdWUsIHRoZSBjYXJ0b2dyYW0sIGlzIG1lYW50IHRvIHJlZHVjZSB0aGUgaXNzdWVzIHdpdGggc21hbGwtbGFyZ2Ugem9uZXMuDQoNCiMjIFZpc3VhbGl6aW5nIGFyZWEgZGF0YTogY2FydG9ncmFtDQoNCkEgY2FydG9ncmFtIGlzIGEgbWFwIHdoZXJlIHRoZSBzaXplIG9mIHRoZSB6b25lcyBpcyBhZGp1c3RlZCBzbyB0aGF0IGluc3RlYWQgb2YgYmVpbmcgdGhlIGxhbmQgYXJlYSwgaXQgaXMgcHJvcG9ydGlvbmFsIHRvIHNvbWUgb3RoZXIgdmFyaWFibGUgb2YgaW50ZXJlc3QuDQoNCkxldHMgaWxsdXN0cmF0ZSB0aGUgaWRlYSBiZWhpbmQgdGhlIGNhcnRvZ3JhbSBoZXJlLg0KDQpJbiB0aGUgbWFwcyBhYm92ZSwgdGhlIHpvbmVzIGFyZSBmYWl0aGZ1bCB0byB0aGVpciBnZW9ncmFwaGljYWwgcHJvcGVydGllcy4gVW5mb3J0dW5hdGVseSwgdGhpcyBvYnNjdXJlZCB0aGUgcmVsZXZhbmNlIG9mIHNtYWxsIHpvbmVzLiBBIGNhcnRvZ3JhbSBjYW4gYmUgd2VpZ2h0ZWQgYnkgYW5vdGhlciB2YXJpYWJsZSwgc2F5IGZvciBpbnN0YW5jZSwgdGhlIHBvcHVsYXRpb24uIEluIHRoaXMgd2F5LCB0aGUgc2l6ZSBvZiB0aGUgem9uZXMgd2lsbCBkZXBlbmQgb24gdGhlIHRvdGFsIHBvcHVsYXRpb24uDQoNCkNhcnRvZ3JhbXMgYXJlIGltcGxlbWVudGVkIGluIFIgaW4gdGhlIHBhY2thZ2UgYGNhcnRvZ3JhbWAuDQpgYGB7cn0NCkNUX3BvcF9jYXJ0b2dyYW0gPC0gY2FydG9ncmFtX2NvbnQoYXMoSGFtaWx0b25fQ1QsICJTcGF0aWFsIiksICJQT1BVTEFUSU9OIikNCmBgYA0KDQpOb3RpY2UgdGhhdCB0aGUgdmFsdWUgb2YgdGhlIGZ1bmN0aW9uIGBjYXJ0b2dyYW1gIChpLmUuLCBpdHMgb3V0cHV0KSBpcyBhIGBTcGF0aWFsUG9seWdvbnNEYXRhRnJhbWVgLiBUaGlzIG9iamVjdCBuZWVkcyB0byBiZSBjb252ZXJ0ZWQgdG8gYW4gb2JqZWN0IG9mIGNsYXNzIGBzZmAgaWYgd2Ugd2lzaCB0byB1c2UgYGdncGxvdDJgIHRvIHZpc3VhbGl6ZSBpdDoNCmBgYHtyfQ0KQ1RfcG9wX2NhcnRvZ3JhbS50IDwtIHN0X2FzX3NmKENUX3BvcF9jYXJ0b2dyYW0pDQpgYGANCg0KUGxvdHRpbmcgdGhlIGNhcnRvZ3JhbToNCmBgYHtyfQ0KZ2dwbG90KENUX3BvcF9jYXJ0b2dyYW0udCkgKyANCiAgZ2VvbV9zZihhZXMoZmlsbCA9IGN1dF9udW1iZXIoSGFtaWx0b25fQ1QkUE9QVUxBVElPTiwgNSkpLCBjb2xvciA9ICJ3aGl0ZSIsIHNpemUgPSAwLjEpICsNCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJZbE9yUmQiKSArDQogIGxhYnMoZmlsbCA9ICJQb3B1bGF0aW9uIikNCmBgYA0KDQpOb3RpY2UgaG93IHRoZSBzaXplIG9mIHRoZSB6b25lcyBoYXMgYmVlbiBhZGp1c3RlZC4NCg0KVGhlIGNhcnRvZ3JhbSBjYW4gYmUgY29tYmluZWQgd2l0aCBjb2xvcmluZyBzY2hlbWVzLCBhcyBpbiBjaG9yb3BsZXRoIG1hcHM6DQpgYGB7cn0NCkNUX3BvcGRlbl9jYXJ0b2dyYW0gPC0gY2FydG9ncmFtKGFzKEhhbWlsdG9uX0NULCAiU3BhdGlhbCIpLCB3ZWlnaHQgPSAiUE9QX0RFTlNJVFkiKQ0KYGBgDQoNClRpZHkgYW5kIHJlc3RvcmUgdGhlIGRhdGE6DQpgYGB7cn0NCkNUX3BvcGRlbl9jYXJ0b2dyYW0udCA8LSBzdF9hc19zZihDVF9wb3BkZW5fY2FydG9ncmFtKQ0KYGBgDQoNCg0KYGBge3J9DQpwb3BfZGVuLmNhcnRvZ3JhbSA8LSBnZ3Bsb3QoQ1RfcG9wZGVuX2NhcnRvZ3JhbS50KSArIA0KICBnZW9tX3NmKGFlcyhmaWxsID0gY3V0X251bWJlcihIYW1pbHRvbl9DVCRQT1BfREVOU0lUWSwgNSkpLGNvbG9yID0gIndoaXRlIiwgc2l6ZSA9IDAuMSkgKw0KICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIllsT3JSZCIpICsNCiAgbGFicyhmaWxsID0gIlBvcCBEZW5zaXR5IikNCnBvcF9kZW4uY2FydG9ncmFtDQpgYGANCg0KQnkgY29tYmluaW5nIGEgY2FydG9ncmFtIHdpdGggY2hvcm9wbGV0aCBtYXBwaW5nLCBpdCBiZWNvbWVzIGVhc2llciB0byBhcHByZWNpYXRlIHRoZSB3YXkgaGlnaCBwb3B1bGF0aW9uIGRlbnNpdHkgaXMgY29uY2VudHJhdGVkIGluIHRoZSBjZW50cmFsIHBhcnRzIG9mIEhhbWlsdG9uLCBCdXJsaW5ndG9uLCBldGMuDQpgYGB7cn0NCmdyaWQuYXJyYW5nZShwb3BfZGVuLm1hcCwgcG9wX2Rlbi5jYXJ0b2dyYW0sIG5yb3cgPSAyKQ0KYGBgDQoNClRoaXMgY29uY2x1ZGVzIHRoaXMgY2hhcHRlci4=